var Transform = require('readable-stream').Transform
var isNode = require('detect-node')
var isSource = require('is-pull-stream').isSource
var toStream = require('pull-to-stream')
var util = require('util');

exports.mkMultipart = function () {
  return new Multipart();
}

exports.getBoundary = function (multipart) {
  return function () {
    return multipart._boundary;
  }
}

var PADDING = '--';
var NEW_LINE = '\r\n';
var NEW_LINE_BUFFER = Buffer.from(NEW_LINE);

function Multipart(options) {
  if (!(this instanceof Multipart)) {
    return new Multipart(options);
  }

  var opts = Object.assign({}, options, { objectMode: true, highWaterMark: 1 })
  Transform.call(this, opts);
  
  this._boundary = this._generateBoundary();
  this._files = [];
  this._draining = false;
}

util.inherits(Multipart, Transform);

Multipart.prototype._transform = function(file, encoding, callback) {
  if (Buffer.isBuffer(file)) {
    this.push(file)
    return callback() // early
  }
  // not a buffer, must be a file
  console.log(this);
  this._files.push(file)
  this._maybeDrain(callback)
}

Multipart.prototype._flush = function() {
  this.push(Buffer.from(PADDING + this._boundary + PADDING + NEW_LINE))
  this.push(null)
}

Multipart.prototype._generateBoundary = function() {
  var boundary = '--------------------------'
    for (var i = 0; i < 24; i++) {
      boundary += Math.floor(Math.random() * 10).toString(16)
    }
  return boundary
}

Multipart.prototype._maybeDrain = function(callback) {
  var self = this;
  if (!this._draining) {
    if (this._files.length) {
      this._draining = true
      const file = this._files.shift()
      this._pushFile(file, function (err) {
        self._draining = false
        if (err) {
          self.emit('error', err)
        } else {
          self._maybeDrain(callback)
        }
      })
    } else {
      this.emit('drained all files')
      callback()
    }
  } else {
    this.once('drained all files', callback)
  }
}

Multipart.prototype._pushFile = function(file, callback) {
    var leading = this._leading(file.headers || {})

    this.push(leading)

    var content = file.content || Buffer.alloc(0)

    if (Buffer.isBuffer(content)) {
      this.push(content)
      this.push(NEW_LINE_BUFFER)
      return callback() // early
    }

    if (isSource(content)) {
      content = toStream.readable(content)
    }

    // From now on we assume content is a stream

    content.once('error', this.emit.bind(this, 'error'))

    content.once('end', function () {
      this.push(NEW_LINE_BUFFER)
      callback()

      // TODO: backpressure!!! wait once self is drained so we can proceed
      // This does not work
      // this.once('drain', () => {
      //   callback()
      // })
    })

    content.on('data', function (data) {
      const drained = this.push(data)
      // Only do the drain dance on Node.js.
      // In browserland, the underlying stream
      // does NOT drain because the request is only sent
      // once this stream ends.
      if (!drained && isNode) {
        content.pause()
        this.once('drain', function() { content.resume()})
      }
    })
}

Multipart.prototype._leading = function(headers) {
    var leading = [PADDING + this._boundary]

    Object.keys(headers).forEach(function (header) {
      leading.push(header + ': ' + headers[header])
    })

    leading.push('')
    leading.push('')

    const leadingStr = leading.join(NEW_LINE)

    return Buffer.from(leadingStr)
}